﻿using System;
using System.Reflection;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Common base class for sections.
    /// </summary>
    /// <remarks>
    /// <see cref="SectionElement"/> represents a rectangular section on a page.
    /// </remarks>
    public abstract class SectionElement : ModelElement, IPositionableElement, IResizableElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="PageElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="PageElement"/>
        /// </para>
        /// <para>
        /// The page element that directly owns this section.
        /// </para>
        /// </param>
        /// <param name="interiorCreator">
        /// <para>
        /// Type: <see cref="SectionInteriorCreator"/>
        /// </para>
        /// <para>
        /// The factory method that will create the interior of this section.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="packagePart"/> or <paramref name="interiorCreator"/> is <see langword="null"/>.
        /// </exception>
        protected SectionElement(XmlPackagePart packagePart, PageElement owner, SectionInteriorCreator interiorCreator)
            : base(packagePart, owner)
        {
            interior = interiorCreator(packagePart, this);
            if (interior == null)
                throw new ArgumentException("The factory method has returned a null interior.", "interiorCreator");

            rotationAngleAttribute.Value = (0.0).ToString(PackageVocabulary.PackageCulture.NumberFormat);
            zIndexAttribute.Value = "0";

            borderThickness.SetOwner(borderThicknessElement);
            margin.SetOwner(marginElement);
            Background = new SolidColorBrush(new Color(255, 255, 255, 255));

            positionableHelper = PositionableHelper.Create(Element);
            resizableHelper = ResizableHelper.Create(Element);
            interiorElement.AppendChild(interior.Element);
        }

        /// <summary>
        /// Creates a new <see cref="PageElement"/> instance.
        /// </summary>
        /// <remarks>
        /// The constructor cannot recreate the interior instance, since it may need the values of the subclass that
        /// were not deserialized yet. Hence, it is the responisibility of the subclass to load the interior
        /// (using the <see cref="ModelElement.LoadElement{T}"/> method) from the <see cref="InteriorElement"/>.
        /// </remarks>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ModelElement"/>
        /// </para>
        /// <para>
        /// The page element that directly owns this section.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// An XML element that represents this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        protected SectionElement(XmlPackagePart packagePart, ModelElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            backgroundElement = element[PackageVocabulary.BackgroundElementName, PackageVocabulary.PackageNamespaceUri];
            borderBrushElement = element[PackageVocabulary.BorderBrushElementName, PackageVocabulary.PackageNamespaceUri];
            borderThicknessElement = element[PackageVocabulary.BorderThicknessElementName, PackageVocabulary.PackageNamespaceUri];
            marginElement = element[PackageVocabulary.MarginElementName, PackageVocabulary.PackageNamespaceUri];

            borderThickness = new Thickness((XmlElement)borderThicknessElement.FirstChild);
            margin = new Thickness((XmlElement)marginElement.FirstChild);

            if (backgroundElement.HasChildNodes)
                background = Brush.Deserialize((XmlElement)backgroundElement.FirstChild);
            if (borderBrushElement.HasChildNodes)
                borderBrush = Brush.Deserialize((XmlElement)borderBrushElement.FirstChild);

            rotationAngleAttribute = element.Attributes[PackageVocabulary.RotationAngleAttribute];
            zIndexAttribute = element.Attributes[PackageVocabulary.ZIndexAttribute];
            typeAttribute = element.Attributes[PackageVocabulary.TypeAttribute];

            positionableHelper = PositionableHelper.Load(element);
            resizableHelper = ResizableHelper.Load(element);

            interiorElement = element[PackageVocabulary.SectionInteriorElementName, PackageVocabulary.PackageNamespaceUri];
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = CreateObjectElementCore(document);

            backgroundElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.BackgroundElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            borderBrushElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.BorderBrushElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            borderThicknessElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.BorderThicknessElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            marginElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.MarginElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            rotationAngleAttribute = document.CreateAttribute(PackageVocabulary.RotationAngleAttribute);
            typeAttribute = document.CreateAttribute(PackageVocabulary.TypeAttribute);
            zIndexAttribute = document.CreateAttribute(PackageVocabulary.ZIndexAttribute);

            typeAttribute.Value = GetType().FullName;

            element.Attributes.Append(rotationAngleAttribute);
            element.Attributes.Append(zIndexAttribute);
            element.Attributes.Append(typeAttribute);

            element.AppendChild(backgroundElement);
            element.AppendChild(borderBrushElement);
            element.AppendChild(borderThicknessElement);
            element.AppendChild(marginElement);

            interiorElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.SectionInteriorElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            element.AppendChild(interiorElement);

            return element;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, this method creates an XML element that will backup the section data.
        /// The common attributes (e.g. type, size, etc.) as well as the section interior element will be added by the base class.
        /// </summary>
        /// <param name="document">
        /// <para>
        /// Type: <see cref="XmlDocument"/>
        /// </para>
        /// <para>
        /// The XML document that is used for creating the elements and attributes.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The created element.
        /// </para>
        /// </returns>
        protected abstract XmlElement CreateObjectElementCore(XmlDocument document);

        #endregion

        #region Protected Properties

        /// <summary>
        /// Gets the XML element that holds the serialized representation of the <see cref="Interior"/>.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the serialized section interior representation.
        /// </para>
        /// </value>
        protected XmlElement InteriorElement
        {
            get { return interiorElement; }
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// When overriden in a derived class, attempts to handle an incoming border request.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Internal.SectionBorderRequest"/>
        /// </para>
        /// <para>
        /// The incoming request.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// <see langword="true"/> if the section has handled the request, <see langword="false"/> otherwise.
        /// </para>
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// The <paramref name="request"/> is <see langword="null"/>.<br/>
        /// -or-<br/>
        /// The request is not recognized.<br/>
        /// -or-<br/>
        /// The request data are not valid.
        /// </exception>
        internal abstract bool HandleBorderRequest(Internal.SectionBorderRequest request);

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the background brush.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Brush"/>
        /// </para>
        /// <para>
        /// The brush that will paint the backround of the section.
        /// This value may be <see langword="null"/> if the section does not have the background.
        /// </para>
        /// </value>
        public Brush Background
        {
            get { return background; }
            set
            {
                if (background != null)
                    background.SetOwner(null);
                background = value;
                if (background != null)
                    background.SetOwner(backgroundElement);

                RaisePropertyChanged("Background");
            }
        }

        /// <summary>
        /// Gets or sets the border brush.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Brush"/>
        /// </para>
        /// <para>
        /// The brush that will paint the border of the section.
        /// This value may be <see langword="null"/> if the section does not have the border.
        /// </para>
        /// </value>
        public Brush BorderBrush
        {
            get { return borderBrush; }
            set
            {
                if (borderBrush != null)
                    borderBrush.SetOwner(null);
                borderBrush = value;
                if (borderBrush != null)
                    borderBrush.SetOwner(borderBrushElement);

                RaisePropertyChanged("BorderBrush");
            }
        }

        /// <summary>
        /// Gets or sets the thickness of the section border.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Thickness"/>
        /// </para>
        /// <para>
        /// The thickness (in centimeters) of the section border.
        /// </para>
        /// </value>
        public Thickness BorderThickness
        {
            get { return borderThickness; }
            set
            {
                borderThickness.SetOwner(null);
                borderThickness = value;
                borderThickness.SetOwner(borderThicknessElement);

                RaisePropertyChanged("BorderThickness");
            }
        }

        /// <summary>
        /// Gets or sets the height of the section.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The height (in centimeters) of the section.
        /// </para>
        /// </value>
        public double Height
        {
            get { return resizableHelper.Height; }
            set
            {
                resizableHelper.Height = value;

                RaisePropertyChanged("Height");
            }
        }

        /// <summary>
        /// Gets the interior of this section.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="SectionInteriorElement"/>
        /// </para>
        /// <para>
        /// The interior of this section.
        /// </para>
        /// </value>
        public SectionInteriorElement Interior
        {
            get { return interior; }
            protected set { interior = value; }
        }

        /// <summary>
        /// Gets or sets the horizontal offset of the section from the page border.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The offset (in centimeters) of the section from the left page border.
        /// </para>
        /// </value>
        public double Left
        {
            get { return positionableHelper.Left; }
            set
            {
                positionableHelper.Left = value;

                RaisePropertyChanged("Left");
            }
        }

        /// <summary>
        /// Gets or sets the margin of the section.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Thickness"/>
        /// </para>
        /// <para>
        /// The margin (in centimeters) of the section.
        /// </para>
        /// </value>
        public Thickness Margin
        {
            get { return margin; }
            set
            {
                margin.SetOwner(null);
                margin = value;
                margin.SetOwner(marginElement);

                RaisePropertyChanged("Margin");
            }
        }

        /// <summary>
        /// Gets or sets the angle of rotation of the section.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The angle (in degrees) of a clock-wise rotation.
        /// </para>
        /// </value>
        public double RotationAngle
        {
            get { return double.Parse(rotationAngleAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                rotationAngleAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("RotationAngle");
            }
        }

        /// <summary>
        /// Gets or sets the vertical offset of the section from the page border.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The offset (in centimeters) of the section from the top page border.
        /// </para>
        /// </value>
        public double Top
        {
            get { return positionableHelper.Top; }
            set
            {
                positionableHelper.Top = value;
                RaisePropertyChanged("Top");
            }
        }

        /// <summary>
        /// Gets or sets the width of the section.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The width (in centimeters) of the section.
        /// </para>
        /// </value>
        public double Width
        {
            get { return resizableHelper.Width; }
            set
            {
                resizableHelper.Width = value;

                RaisePropertyChanged("Width");
            }
        }

        /// <summary>
        /// Gets or sets the z-index of the section.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The z-index of the section. Default is 0.<br/>
        /// The sections with higher <see cref="ZIndex"/> value will be drawn
        /// on top of the sections with lower <see cref="ZIndex"/> value.
        /// </para>
        /// </value>
        public int ZIndex
        {
            get { return int.Parse(zIndexAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                zIndexAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("ZIndex");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Background"/> property.
        /// </summary>
        private Brush background;
        /// <summary>
        /// Holds the XML element that holds the background brush.
        /// </summary>
        private XmlElement backgroundElement;
        /// <summary>
        /// Holds the value of the <see cref="BorderBrush"/> property.
        /// </summary>
        private Brush borderBrush;
        /// <summary>
        /// Holds the XML element that holds the border brush.
        /// </summary>
        private XmlElement borderBrushElement;
        /// <summary>
        /// Holds the value of the <see cref="BorderThickness"/> property.
        /// </summary>
        private Thickness borderThickness;
        /// <summary>
        /// Holds the XML element that holds the <see cref="BorderThickness"/> element.
        /// </summary>
        private XmlElement borderThicknessElement;
        /// <summary>
        /// Holds the interior of this section.
        /// </summary>
        private SectionInteriorElement interior;
        /// <summary>
        /// Holds the XML element that holds the section interior.
        /// </summary>
        private XmlElement interiorElement;
        /// <summary>
        /// Holds the value of the <see cref="Margin"/> property.
        /// </summary>
        private Thickness margin;
        /// <summary>
        /// Holds the XML element that holds the <see cref="Margin"/> element.
        /// </summary>
        private XmlElement marginElement;
        /// <summary>
        /// Holds the positionable element helper.
        /// </summary>
        private PositionableHelper positionableHelper;
        /// <summary>
        /// Holds the resizable element helper.
        /// </summary>
        private ResizableHelper resizableHelper;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="RotationAngle"/> property.
        /// </summary>
        private XmlAttribute rotationAngleAttribute;
        /// <summary>
        /// Holds the XML attribute that holds the name of the section type.
        /// </summary>
        private XmlAttribute typeAttribute;
        /// <summary>
        /// Holds the value of the <see cref="ZIndex"/> property.
        /// </summary>
        private XmlAttribute zIndexAttribute;

        #endregion
    }

    /// <summary>
    /// Represents a factory method that creates an instance of a <see cref="SectionInteriorElement"/>.
    /// </summary>
    /// <param name="packagePart">
    /// <para>
    /// Type: <see cref="XmlPackagePart"/>
    /// </para>
    /// <para>
    /// The package part that owns the element.
    /// </para>
    /// </param>
    /// <param name="section">
    /// <para>
    /// Type: <see cref="SectionElement"/>
    /// </para>
    /// <para>
    /// The section that directly owns the element.
    /// </para>
    /// </param>
    /// <returns>
    /// <para>
    /// Type: <see cref="SectionInteriorElement"/>
    /// </para>
    /// <para>
    /// The created section interior element.
    /// </para>
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// <paramref name="packagePart"/> or <paramref name="section"/> is <see langword="null"/>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// The <paramref name="section"/> has not the expected type.
    /// </exception>
    public delegate SectionInteriorElement SectionInteriorCreator(XmlPackagePart packagePart, SectionElement section);
}
